import {
  Controller,
  Post,
  Body,
  UseGuards,
  Request,
  HttpCode,
  HttpStatus,
  UnauthorizedException,
  Logger,
  BadRequestException,
  HttpException,
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { JwtAuthGuard } from './guards/jwt-auth.guard';
import { RegisterDto, LoginDto, AdminLoginDto, RenewTokenDto, LogoutDto } from './dto';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiBody } from '@nestjs/swagger';
import { UsersService } from '../users/users.service';
import { SmsService } from '../common/services/sms.service';
import { PasswordResetRequestDto } from '../users/dto/password-reset-request.dto';
import { PasswordResetConfirmDto } from '../users/dto/password-reset-confirm.dto';
import * as argon2 from 'argon2';
import {
  AdminAuthTokenResponseDto,
  UserAuthTokenResponseDto,
  AdminTokenRenewalResponseDto,
  UserTokenRenewalResponseDto,
  RegisterResponseDto,
  LogoutResponseDto,
  PasswordResetRequestResponseDto,
  PasswordResetConfirmResponseDto,
} from './dto/auth-response.dto';
import { JwtPayload } from './interfaces/jwt-payload.interface';
import { VerifyTokenDto } from './dto/verify-token.dto';
import {
  VerifyTokenResponseDto,
  VerifyTokenErrorResponseDto,
} from './dto/verify-token-response.dto';

@ApiTags('Authentication')
@Controller('auth')
export class AuthController {
  private readonly logger = new Logger(AuthController.name);

  constructor(
    private readonly authService: AuthService,
    private readonly usersService: UsersService,
    private readonly smsService: SmsService,
  ) {}

  /**
   * Register a new user
   * @param registerDto Registration data
   * @returns Created user
   */
  @Post('register')
  @ApiOperation({ summary: '注册新用户' })
  @ApiBody({ type: RegisterDto })
  @ApiResponse({
    status: 201,
    description: 'User successfully registered',
    type: RegisterResponseDto,
  })
  @ApiResponse({ status: 400, description: 'Bad request' })
  @ApiResponse({ status: 409, description: 'Username already exists or device already assigned' })
  async register(@Body() registerDto: RegisterDto): Promise<RegisterResponseDto> {
    this.logger.log(`Registering user with username: ${registerDto.username}`);
    const user = await this.authService.register(registerDto);
    return {
      statusCode: HttpStatus.CREATED,
      message: 'User registered successfully',
      data: user,
    };
  }

  /**
   * Admin login
   * @param adminLoginDto Admin login data
   * @returns JWT token and refresh token
   */
  @Post('admin/login')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: '管理员登录' })
  @ApiBody({ type: AdminLoginDto })
  @ApiResponse({
    status: 200,
    description: 'Admin successfully logged in with access token and refresh token',
    type: AdminAuthTokenResponseDto,
  })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  async adminLogin(@Body() adminLoginDto: AdminLoginDto): Promise<AdminAuthTokenResponseDto> {
    this.logger.log(`Admin login attempt for username: ${adminLoginDto.username}`);
    return this.authService.adminLogin(adminLoginDto);
  }

  /**
   * User login
   * @param loginDto User login data
   * @returns JWT token and refresh token
   */
  @Post('login')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: '用户登录' })
  @ApiBody({ type: LoginDto })
  @ApiResponse({
    status: 200,
    description: 'User successfully logged in',
    type: UserAuthTokenResponseDto,
  })
  @ApiResponse({ status: 401, description: 'Unauthorized - Invalid username or password' })
  @ApiResponse({
    status: 403,
    description: 'Forbidden - Device not found, not assigned to user, or inactive',
  })
  async login(@Body() loginDto: LoginDto): Promise<UserAuthTokenResponseDto> {
    this.logger.log(`Login attempt for username: ${loginDto.username}`);
    return this.authService.login(loginDto);
  }

  /**
   * Renew access token
   * @param renewTokenDto Token renewal data
   * @returns New access token
   */
  @Post('token/renew')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: '更新访问令牌' })
  @ApiBody({ type: RenewTokenDto })
  @ApiResponse({
    status: 200,
    description: 'Token renewed successfully for admin users',
    type: AdminTokenRenewalResponseDto,
  })
  @ApiResponse({
    status: 200,
    description: 'Token renewed successfully for regular users',
    type: UserTokenRenewalResponseDto,
  })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  async renewToken(
    @Body() renewTokenDto: RenewTokenDto,
  ): Promise<AdminTokenRenewalResponseDto | UserTokenRenewalResponseDto> {
    this.logger.log('Token renewal request');
    return this.authService.renewToken(renewTokenDto);
  }

  /**
   * Logout
   * @param req Request object containing the user from JWT
   * @returns Success message
   */
  @Post('logout')
  @UseGuards(JwtAuthGuard)
  @HttpCode(HttpStatus.OK)
  @ApiBearerAuth()
  @ApiOperation({ summary: '退出登录' })
  @ApiResponse({
    status: 200,
    description: 'Successfully logged out',
    type: LogoutResponseDto,
  })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 404, description: 'Session not found' })
  async logout(@Request() req: { user: JwtPayload }): Promise<LogoutResponseDto> {
    const { sessionId } = req.user;
    if (!sessionId) {
      throw new UnauthorizedException('Invalid session');
    }

    this.logger.log(`Logout attempt for user: ${req.user.username}`);
    const logoutDto = new LogoutDto();
    logoutDto.sessionId = sessionId;
    return this.authService.logout(logoutDto);
  }

  /**
   * Request password reset
   * @param resetRequestDto Password reset request data
   */
  @Post('password-reset/request')
  @HttpCode(HttpStatus.ACCEPTED)
  @ApiOperation({ summary: '通过短信请求重置密码' })
  @ApiBody({ type: PasswordResetRequestDto })
  @ApiResponse({
    status: 202,
    description: 'Verification code sent',
    type: PasswordResetRequestResponseDto,
  })
  @ApiResponse({ status: 400, description: 'Bad request' })
  @ApiResponse({ status: 429, description: 'Too many attempts' })
  async requestReset(
    @Body() resetRequestDto: PasswordResetRequestDto,
  ): Promise<PasswordResetRequestResponseDto> {
    this.logger.log(`Password reset requested for phone: ${resetRequestDto.phone}`);

    // Find user by phone number
    const user = await this.usersService.findByPhone(resetRequestDto.phone);

    // Always return the same response to prevent user enumeration
    const response: PasswordResetRequestResponseDto = {
      message: 'If a user exists with this phone number, they will receive a verification code',
    };

    if (!user) {
      // Don't reveal whether the phone number exists
      this.logger.warn(`Password reset requested for non-existent phone: ${resetRequestDto.phone}`);
      return response;
    }

    try {
      await this.smsService.sendVerificationCode(resetRequestDto.phone);
      this.logger.log(`Verification code sent to phone: ${resetRequestDto.phone}`);
      return response;
    } catch (error) {
      if (error.message.includes('Too many attempts')) {
        throw new HttpException(
          'Too many attempts. Please try again later.',
          HttpStatus.TOO_MANY_REQUESTS,
        );
      }
      throw error;
    }
  }

  /**
   * Confirm password reset
   * @param confirmDto Password reset confirmation data
   */
  @Post('password-reset/confirm')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: '使用验证码确认密码重置' })
  @ApiBody({ type: PasswordResetConfirmDto })
  @ApiResponse({
    status: 200,
    description: 'Password reset successful',
    type: PasswordResetConfirmResponseDto,
  })
  @ApiResponse({ status: 400, description: 'Invalid or expired verification code' })
  async confirmReset(
    @Body() confirmDto: PasswordResetConfirmDto,
  ): Promise<PasswordResetConfirmResponseDto> {
    this.logger.log(`Password reset confirmation for phone: ${confirmDto.phone}`);

    // Find user by phone number
    const user = await this.usersService.findByPhone(confirmDto.phone);
    if (!user) {
      // Don't reveal that the user doesn't exist
      this.logger.warn(
        `Password reset confirmation attempted for non-existent phone: ${confirmDto.phone}`,
      );
      throw new BadRequestException('Invalid or expired verification code');
    }

    // Verify the code
    const isCodeValid = await this.smsService.verifyCode(confirmDto.phone, confirmDto.code);
    if (!isCodeValid) {
      throw new BadRequestException('Invalid or expired verification code');
    }

    // Hash the new password
    const passwordHash = await argon2.hash(confirmDto.newPassword);

    // Update the password and invalidate all sessions
    await this.usersService.resetPassword(user.id, passwordHash);

    this.logger.log(`Password reset successful for user ID: ${user.id}`);

    return {
      message: 'Password has been reset successfully',
    };
  }

  /**
   * Verify access token
   * @param verifyTokenDto Token verification data
   * @returns User information if token is valid
   */
  @Post('verify-token')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: '验证访问令牌' })
  @ApiBody({ type: VerifyTokenDto })
  @ApiResponse({
    status: 200,
    description: 'Token successfully verified',
    type: VerifyTokenResponseDto,
  })
  @ApiResponse({
    status: 401,
    description: 'Unauthorized - Invalid token',
    type: VerifyTokenErrorResponseDto,
  })
  async verifyToken(@Body() verifyTokenDto: VerifyTokenDto): Promise<VerifyTokenResponseDto> {
    this.logger.log('Token verification request');
    return this.authService.verifyToken(verifyTokenDto.accessToken);
  }
}
